package ch.ethz.fcl.metrobuzz.data.transit.bus;

import java.util.ArrayList;
import java.util.List;

import ch.ethz.fcl.metrobuzz.data.transit.Stop;
import ch.ethz.fcl.metrobuzz.data.transit.TransitRoute;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

public class CalibratedBusSchedule {
	private TransitRoute route;
	private RawBusSchedule rawSchedule;

	// records each bus passes bus tops times
	private List<List<Float>> times;

	public CalibratedBusSchedule(RawBusSchedule rawSchedule) {
		this.route = rawSchedule.getRoute();
		this.rawSchedule = rawSchedule;

		times = new ArrayList<List<Float>>();
	}

	public void cleanup() {
		List<Stop> stops = route.getStops();

		List<BusPair> pairs = rawSchedule.getBusPairs();
		for (BusPair p : pairs) {
			List<Float> busTimes = new ArrayList<Float>();
			for (int i = 0; i < stops.size(); i++) {
				List<Float> stopTimes = rawSchedule.getTapTime(stops.get(i));

				if (i == 0)
					stopTimes = rawSchedule.getTapInTime(stops.get(i));
				else if (i == stops.size() - 1)
					stopTimes = rawSchedule.getTapOutTime(stops.get(i));

				Float stopTime = -1.0f;

				// don't have stops bus passing times
				if (stopTimes == null) {
					stopTime = getTime(p, i);
				} else {
					for (int j = 0; j < stopTimes.size(); j++) {
						Float t = stopTimes.get(j);
						if (t <= p.getEndTime() && t >= p.getStartTime()) {
							if (busTimes.size() == 0) {
								stopTime = t;
								break;
							}
							// make sure time is larger than previous time
							else if (t > busTimes.get(busTimes.size() - 1)) {
								stopTime = t;
								break;
							}
						}
					}

					// if did not find the time slot
					if (stopTime == -1.0f) {
						stopTime = getTime(p, i);
						// if (this.rawSchedule.getBusNum() == 9324)
						// System.out.println("stop time "
						// + MBTools.formatTimeInSeconds(stopTime)
						// + " at stop "
						// + route.getStops().get(i).getId());
					}
				}

				busTimes.add(stopTime);
			}

			times.add(busTimes);
		}
	}

	/**
	 * return the time the bus pass by this stop. find the closest bus stop that
	 * this bus has record within this bus pair, and assume linear speed
	 * 
	 * @param p
	 *            bus pair
	 * @param index
	 *            stop index in the stop list
	 * @return
	 */
	private float getTime(BusPair p, int index) {
		List<Stop> stops = route.getStops();
		Stop firstTapInStop = p.getFirstTapIn();
		Stop lastTapOutStop = p.getLastTapOut();

		int firstStopIndex = getStopIndex(firstTapInStop, true);
		int lastStopIndex = getStopIndex(lastTapOutStop, false);

		float time = -1;

		try {
			if (index < firstStopIndex) {
				StopTime nextStopWithTime = findNextStopWithTime(p,
						firstStopIndex);

				int incre = 1;
				while (nextStopWithTime.time == p.getStartTime()
						&& index + incre < stops.size()) {
					nextStopWithTime = findNextStopWithTime(p, firstStopIndex
							+ incre);
					incre++;
				}

				float dist1 = route.getStopDist(stops.get(index),
						firstTapInStop);
				float dist2 = route.getStopDist(firstTapInStop,
						nextStopWithTime.stop);

				time = p.getStartTime()
						- (nextStopWithTime.time - p.getStartTime()) / dist2
						* dist1;
			} else if (index > lastStopIndex) {
				StopTime lastStopWithTime = findLastStopWithTime(p,
						lastStopIndex);

				int incre = -1;
				while (lastStopWithTime.time == p.getEndTime()
						&& index + incre > 0) {
					lastStopWithTime = findLastStopWithTime(p, lastStopIndex
							+ incre);
					incre--;
				}

				float dist1 = route.getStopDist(lastTapOutStop,
						stops.get(index));
				float dist2 = route.getStopDist(lastStopWithTime.stop,
						lastTapOutStop);

				time = p.getEndTime()
						+ (p.getEndTime() - lastStopWithTime.time) / dist2
						* dist1;
			} else {
				StopTime lastStopWithTime = findLastStopWithTime(p, index);
				StopTime nextStopWithTime = findNextStopWithTime(p, index);

				int incre = 1;
				while (nextStopWithTime.time == lastStopWithTime.time
						&& index + incre < stops.size()) {
					nextStopWithTime = findNextStopWithTime(p, index + incre);
					incre++;
				}

				incre = -1;
				while (nextStopWithTime.time == lastStopWithTime.time
						&& index + incre > 0) {
					lastStopWithTime = findLastStopWithTime(p, lastStopIndex
							+ incre);
					incre--;
				}

				float dist1 = route.getStopDist(lastStopWithTime.stop,
						stops.get(index));
				float dist2 = route.getStopDist(stops.get(index),
						nextStopWithTime.stop);

				time = lastStopWithTime.time
						+ (nextStopWithTime.time - lastStopWithTime.time)
						/ (dist1 + dist2) * dist1;
			}
		} catch (Exception e) {
			// System.out.print(rawSchedule.toString());
			// System.out.println("Stop " +
			// route.getStops().get(index).getId());
			// System.out.println(p.toString() + "\n");
		}
		return time;
	}

	public List<List<Float>> getTransitSchedule() {
		return times;
	}

	/**
	 * find the next stop that this bus passes and the time passing it
	 * 
	 * @param p
	 * @param index
	 * @return
	 */

	private StopTime findNextStopWithTime(BusPair p, int index) {
		List<Stop> stops = route.getStops();

		StopTime st = null;
		int count = index + 1;
		while (count < stops.size()) {
			List<Float> stopTimes = rawSchedule.getTapTime(stops.get(count));

			if (count == 0)
				stopTimes = rawSchedule.getTapInTime(stops.get(count));
			else if (count == stops.size() - 1)
				stopTimes = rawSchedule.getTapOutTime(stops.get(count));

			for (int j = 0; j < stopTimes.size(); j++) {
				Float t = stopTimes.get(j);
				if (t <= p.getEndTime() && t >= p.getStartTime()) {
					st = new StopTime(stops.get(count), t);
					break;
				}
			}

			if (st != null)
				break;
			count++;
		}
		return st;
	}

	/**
	 * find the last stop that this bus passes and the time passing it
	 * 
	 * @param p
	 * @param index
	 * @return
	 */

	private StopTime findLastStopWithTime(BusPair p, int index) {
		List<Stop> stops = route.getStops();

		StopTime st = null;
		int count = index - 1;
		while (count >= 0) {
			List<Float> stopTimes = rawSchedule.getTapTime(stops.get(count));

			if (count == 0)
				stopTimes = rawSchedule.getTapInTime(stops.get(count));
			else if (count == stops.size() - 1)
				stopTimes = rawSchedule.getTapOutTime(stops.get(count));

			if (stopTimes != null)
				for (int j = 0; j < stopTimes.size(); j++) {
					Float t = stopTimes.get(j);
					if (t <= p.getEndTime() && t >= p.getStartTime()) {
						st = new StopTime(stops.get(count), t);
						break;
					}
				}

			if (st != null)
				break;
			count--;
		}
		return st;
	}

	private int getStopIndex(Stop stop, boolean start) {
		int index = -1;
		List<Stop> stops = route.getStops();

		for (int i = 0; i < stops.size(); i++) {
			if (stops.get(i).equals(stop)) {
				index = i;
				if (start)
					break;
			}
		}

		return index;
	}

	/**
	 * return the list of times the bus pass by indexed stop in the route
	 * 
	 * @param index
	 *            index of the stop in the route
	 * @return
	 */
	public List<Float> getPassingTime(int index) {
		List<Float> passTime = new ArrayList<Float>();

		for (List<Float> busTimes : times) {
			passTime.add(busTimes.get(index));
		}

		return passTime;
	}

	public class StopTime {
		Stop stop;
		float time;

		public StopTime(Stop stop, float time) {
			this.stop = stop;
			this.time = time;
		}
	}

	@Override
	public String toString() {
		String out = "Calibrated Bus " + rawSchedule.getBusNum() + " in line "
				+ route.getId() + ":\n";

		for (List<Float> busTimes : times) {
			for (int i = 0; i < busTimes.size(); i++) {
				Float stopTime = busTimes.get(i);
				out += MBTools.formatTimeInSeconds(stopTime);
				if (i < busTimes.size() - 1)
					out += "->";
			}
			out += "\n";

			out += "Calibrated whole trip time: "
					+ MBTools.formatTimeInSeconds(busTimes.get(0))
					+ "->"
					+ MBTools
							.formatTimeInSeconds(busTimes.get(busTimes.size() - 1))
					+ " in "
					+ MBTools
							.formatTimeInSeconds(busTimes.get(busTimes.size() - 1)
									- busTimes.get(0)) + "\n";
		}

		return out;
	}
}
